/*
    Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies)
    Copyright (C) 2013 Company 100, Inc.
    Copyright (C) 2014 Sony Computer Entertainment Inc.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "config.h"

#if USE(COORDINATED_GRAPHICS) && ENABLE(MANX_AC_PROCESS)
#include "CoordinatedLayerTreeHostProxyManx.h"

#include "ACProcess.h"
#include "ArgumentCoder.h"
#include "CoordinatedLayerTreeHostMessages.h"
#include "CoordinatedLayerTreeHostProxyManxMessages.h"
#include "WebCoreArgumentCoders.h"
#include <WebCore/CoordinatedGraphicsState.h>
#include <WebCore/GLPlatformContext.h>
#include <WebCore/OpenGLManx.h>
#include <WebCore/TextureMapperGL.h>

namespace WebKit {

using namespace WebCore;

static void makeSharedGLContextCurrent()
{
    static OwnPtr<WebCore::GLPlatformContext> context;
    static OwnPtr<WebCore::GLPlatformSurface> surface;

    if (!context) {
        ASSERT(!surface);
        context = GLPlatformContext::createContext(GraphicsContext3D::RenderOffscreen);
        surface = GLPlatformSurface::createOffScreenSurface();
        if (!context || !surface)
            CRASH();

        if (!context->initialize(surface.get()))
            CRASH();
    }
    
    ASSERT(context);
    ASSERT(surface);

    if (context->isCurrentContext())
        return;

    if (!context->makeCurrent(surface.get()))
        CRASH();
}

class LayerFlushScheduler {
public:
    typedef CoordinatedLayerTreeHostProxyManx Client;

    LayerFlushScheduler()
        : m_scheduled(false)
    {
        m_threadIdentifier = createThread(threadFunc, this, "SceLayerFlushScheduler");
        if (!m_threadIdentifier)
            CRASH();
    }

    void scheduleLayerFlush(Client* client)
    {
        m_clients.add(client);
        m_scheduled = true;
    }

    void cancelPendingLayerFlush(Client* client)
    {
        m_clients.remove(client);
    }

private:
    static void threadFunc(void* that)
    {
        Manx::DisplayRefreshMonitor* monitor = new Manx::DisplayRefreshMonitor;
        if (!monitor)
            CRASH();

        int priority = -1; // Slightly higher than default.
        monitor->start(priority); 
        while (true) {
            monitor->waitVblank();
            displayLinkFired(that);
        }
        monitor->stop();
        delete monitor;
    }

    static void displayLinkFired(void* that)
    {
        if (reinterpret_cast<LayerFlushScheduler*>(that)->m_scheduled)
            callOnMainThread(doLayerFlush, that);
    }

    static void doLayerFlush(void* that)
    {
        reinterpret_cast<LayerFlushScheduler*>(that)->doLayerFlush();
    }

    void doLayerFlush()
    {
        Vector<Client*> clients;

        for (ClientSet::iterator it = m_clients.begin(); it != m_clients.end(); ++it) {
            Client* client = *it;
            if (client)
                clients.append(client);
        }
        m_clients.clear();
        m_scheduled = false;

        size_t size = clients.size();
        if (!size)
            return;

        makeSharedGLContextCurrent();
        glFinish();
        for (size_t i = 0; i < size; i++)
            clients[i]->displayRefreshFired(0);
        glFlush();
        sce::CanvasUtil::flush();
        for (size_t i = 0; i < size; i++)
            clients[i]->updateCanvasHandle();
    }

    WTF::ThreadIdentifier m_threadIdentifier;
    volatile bool m_scheduled;
    typedef HashSet<Client*> ClientSet;
    ClientSet m_clients;
};

LayerFlushScheduler* layerFlushScheduler()
{
    static LayerFlushScheduler* scheduler = new LayerFlushScheduler;
    if (!scheduler)
        CRASH();
    return scheduler;
}

CoordinatedLayerTreeHostProxyManx::CoordinatedLayerTreeHostProxyManx(uint64_t pageID)
    : m_pageID(pageID)
    , m_scene(adoptRef(new CoordinatedGraphicsScene(this)))
    , m_canvasID(sce::Canvas::kInvalidIdValue)
    , m_canvasHandle(sce::Canvas::kInvalidHandle)
    , m_canvasBuffer(0)
    , m_backgroundColor(Color::white)
    , m_isVisible(true)
    , m_isActive(false)
{
    ACProcess::shared().addMessageReceiver(Messages::CoordinatedLayerTreeHostProxyManx::messageReceiverName(), m_pageID, this);
    ACProcess::shared().send(Messages::CoordinatedLayerTreeHost::DidCreateProxy(), m_pageID);
    m_scene->setActive(true);
}

CoordinatedLayerTreeHostProxyManx::~CoordinatedLayerTreeHostProxyManx()
{
    cancelPendingLayerFlush();

    makeSharedGLContextCurrent();

    ACProcess::shared().removeMessageReceiver(Messages::CoordinatedLayerTreeHostProxyManx::messageReceiverName(), m_pageID);
    m_scene->detach();
    
    if (m_canvasBuffer) {
        m_canvasBuffer->terminate();
        delete m_canvasBuffer;
        m_canvasBuffer = nullptr;
    }

    sce::Canvas::releaseId(m_canvasID);
    m_canvasID = sce::Canvas::kInvalidIdValue;
}

// DisplayRefreshMonitorClient
void CoordinatedLayerTreeHostProxyManx::displayRefreshFired(double)
{
    doLayerFlush();
    if (isVisible())
        scheduleLayerFlush();
}

void CoordinatedLayerTreeHostProxyManx::scheduleLayerFlush()
{
    layerFlushScheduler()->scheduleLayerFlush(this);
}

void CoordinatedLayerTreeHostProxyManx::cancelPendingLayerFlush()
{
    layerFlushScheduler()->cancelPendingLayerFlush(this);
}

// 
void CoordinatedLayerTreeHostProxyManx::doLayerFlush()
{
    // Since the Y axis goes up in OpenGL's texture coodinate, vertical flip is required.
    const bool shouldFlipY = true;
    
    if (m_viewSize.isEmpty())
        return;

    ensureCanvasBuffer(m_viewSize);
    FloatRect viewport(IntPoint(), m_viewSize);
    TransformationMatrix matrix;
    matrix.makeIdentity();

    m_canvasBuffer->activateAsFrameBuffer();

    glViewport(0, 0, m_canvasBuffer->width(), m_canvasBuffer->height());

    float r, g, b, a;
    m_backgroundColor.getRGBA(r, g, b, a);
    m_scene->setDrawsBackground(false);
    glClearColor(r, g, b, a);
    glClear(GL_COLOR_BUFFER_BIT);

    TextureMapper::PaintFlags paintFlags = 0;
    if (shouldFlipY)
        paintFlags |= TextureMapperGL::PaintingMirrored;

    m_scene->paintToCurrentGLContext(matrix, 1, viewport, paintFlags);
    
    m_canvasBuffer->activateAsCanvas(m_canvasID);
    m_canvasBuffer->rotateBuffers();

#if EGL_SCE_piglet_memory_info
    EGLMEMORYINFO_t memoryInfo = { };
    memoryInfo.uSize = sizeof(memoryInfo);
    eglMemoryInfo(&memoryInfo);
    if (memoryInfo.uMaxAllowed > 0) {
        GLMemoryInfo glMemoryInfo;
        glMemoryInfo.heap = memoryInfo.uHeap;
        glMemoryInfo.texture = memoryInfo.uTexture;
        glMemoryInfo.surfaces = memoryInfo.uSurfaces;
        glMemoryInfo.programs = memoryInfo.uPrograms;
        glMemoryInfo.buffers = memoryInfo.uBuffers;
        glMemoryInfo.commandBuffers = memoryInfo.uCommandBuffers;
        glMemoryInfo.total = memoryInfo.uTotal;
        glMemoryInfo.maxAllowed = memoryInfo.uMaxAllowed;

        if (m_glMemoryInfo != glMemoryInfo) {
            m_glMemoryInfo = glMemoryInfo;
            ACProcess::shared().send(Messages::CoordinatedLayerTreeHost::UpdateGLMemoryInfo(glMemoryInfo), pageID());
        }
    }
#endif
}

void CoordinatedLayerTreeHostProxyManx::ensureCanvasBuffer(const WebCore::IntSize& canvasSize)
{
    if (!m_canvasBuffer)
        m_canvasBuffer = new CanvasBuffer;
    if (!m_canvasBuffer)
        CRASH();

    if (canvasSize == IntSize(m_canvasBuffer->width(), m_canvasBuffer->height()))
        return;

    makeSharedGLContextCurrent();
    m_canvasBuffer->initialize(canvasSize.width(), canvasSize.height());

    sce::Canvas::releaseId(m_canvasID);
    m_canvasID = sce::Canvas::acquireId();
    m_canvasHandle = sce::Canvas::kInvalidHandle;
    ACProcess::shared().send(Messages::CoordinatedLayerTreeHost::SetCanvasHandle(sce::Canvas::kInvalidHandle), pageID());
}

void CoordinatedLayerTreeHostProxyManx::invalidateCanvas()
{
    // We're going to make the canvas invisible but it may be delayed to 
    // the next flush. Thus we send back a SetCanvasHandle message with
    // kInvalidHandle immediately.
    m_canvasHandle = sce::Canvas::kInvalidHandle;
    ACProcess::shared().send(Messages::CoordinatedLayerTreeHost::SetCanvasHandle(sce::Canvas::kInvalidHandle), pageID());
}

void CoordinatedLayerTreeHostProxyManx::updateCanvasHandle()
{
    if (!isVisible())
        return;
    sce::Canvas::Handle canvasHandle = sce::Canvas::handle(m_canvasID);
    if (m_canvasHandle != canvasHandle) {
        m_canvasHandle = canvasHandle;
        ACProcess::shared().send(Messages::CoordinatedLayerTreeHost::SetCanvasHandle(canvasHandle), pageID());
    }
}

// 
void CoordinatedLayerTreeHostProxyManx::updateViewport()
{
//    m_drawingAreaProxy->updateViewport();
}

// WebProcess -> ACProcess
void CoordinatedLayerTreeHostProxyManx::commitCoordinatedGraphicsState(const CoordinatedGraphicsState& graphicsState)
{
    dispatchUpdate(bind(&CoordinatedGraphicsScene::commitSceneState, m_scene.get(), graphicsState));
    updateViewport();
#if USE(TILED_BACKING_STORE)
//    m_drawingAreaProxy->page()->didRenderFrame(graphicsState.contentsSize, graphicsState.coveredRect);
#endif
    scheduleLayerFlush();
}

void CoordinatedLayerTreeHostProxyManx::setBackgroundColor(const Color& color)
{
    m_backgroundColor = color;
    dispatchUpdate(bind(&CoordinatedGraphicsScene::setBackgroundColor, m_scene.get(), color));
}

// FIXME: This is not necessary for ideal UI side compositing.
void CoordinatedLayerTreeHostProxyManx::sizeDidChange(const WebCore::IntSize& newSize)
{
    if (m_viewSize == newSize)
        return;

    setVisibleContentsRect(FloatRect(IntRect(IntPoint(), newSize)), FloatPoint());
    m_viewSize = newSize;
    scheduleLayerFlush();
}

void CoordinatedLayerTreeHostProxyManx::setVisible(bool isVisible)
{
    if (m_isVisible == isVisible)
        return;

    m_isVisible = isVisible;

    if (!m_isVisible)
        invalidateCanvas();
}

void CoordinatedLayerTreeHostProxyManx::setActive(bool isActive)
{
    if (m_isActive == isActive)
        return;
    m_isActive = isActive;
}

// ACProcess -> WebProcess
void CoordinatedLayerTreeHostProxyManx::setVisibleContentsRect(const FloatRect& rect, const FloatPoint& trajectoryVector)
{
    // Inform the renderer to adjust viewport-fixed layers.
    dispatchUpdate(bind(&CoordinatedGraphicsScene::setScrollPosition, m_scene.get(), rect.location()));

    if (rect == m_lastSentVisibleRect && trajectoryVector == m_lastSentTrajectoryVector)
        return;

    ACProcess::shared().send(Messages::CoordinatedLayerTreeHost::SetVisibleContentsRect(rect, trajectoryVector), pageID());
    m_lastSentVisibleRect = rect;
    m_lastSentTrajectoryVector = trajectoryVector;
}

void CoordinatedLayerTreeHostProxyManx::renderNextFrame()
{
    ACProcess::shared().send(Messages::CoordinatedLayerTreeHost::RenderNextFrame(), pageID());
}

void CoordinatedLayerTreeHostProxyManx::purgeBackingStores()
{
    ACProcess::shared().send(Messages::CoordinatedLayerTreeHost::PurgeBackingStores(), pageID());
}

void CoordinatedLayerTreeHostProxyManx::commitScrollOffset(uint32_t layerID, const IntSize& offset)
{
    ACProcess::shared().send(Messages::CoordinatedLayerTreeHost::CommitScrollOffset(layerID, offset), pageID());
}

// Update
void CoordinatedLayerTreeHostProxyManx::dispatchUpdate(const Function<void()>& function)
{
    m_scene->appendUpdate(function);
}

}
#endif // USE(COORDINATED_GRAPHICS) && ENABLE(MANX_AC_PROCESS)
